﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Hids.Enums;

namespace Hids.Classes
{
    /// <summary>
    /// This class includes the methods that check the system logs for potential attacks
    /// </summary>
    public class EventLogs
    {
        #region Private Properties

        /// <summary>
        /// The number of bad logins until the login attempts are considered an attack
        /// </summary>
        private static int _numberOfBadLogins = 5;

        /// <summary>
        /// Whether or not a successful login resets the brute force login counter
        /// </summary>
        private static bool _successfulLoginResetsBruteCounter = true;
        
        /// <summary>
        /// The amount of time before the brute force login timer should be reset
        /// </summary>
        private static TimeSpan _timeBeforeResetingBruteCounter = new TimeSpan(0, 1, 0, 0);

        /// <summary>
        /// Holds the last time a brute force attack was detected.  That way several notifications/logs won't be written to for the same attack
        /// </summary>
        private static DateTime _lastBruteForceAttackDetected = new DateTime(1, 1, 1);

        /// <summary>
        /// The amount of time to wait before a brute force attack is considered multiple attacks
        /// </summary>
        private static TimeSpan _waitTimeBeforeANewBruteForceAttack = new TimeSpan(1, 0, 0);

        /// <summary>
        /// Holds the last time that the account changes were last checked
        /// </summary>
        private static DateTime _accountChangesLastChecked = DateTime.Now;

        #endregion

        #region Private Methods

        /// <summary>
        /// Checks the event logs for a possible brute force attack
        /// </summary>
        /// <see cref="http://support.microsoft.com/kb/174074"/>
        /// <returns>Whether or not a brute force attack was detected</returns>
        public static bool DetectBruteForceAttack()
        {
            Logs.WriteMessageToLog("Checking Event Logs for possible brute force attack"); //write a message to the logs

            List<EventLog> eventLogs = new List<EventLog>(EventLog.GetEventLogs());
            int badLoginAttempts = 0; //holds the number of login failures
            DateTime lastLoginFailure = DateTime.Now;//holds the time that the last login failure was detected
            bool loginFailureDetected = false; //whether or not at least 1 login failure has been detected in the last time frame
            string notificationMessage = null;//holds message to write to HIDS log.

            foreach (EventLog eventLog in eventLogs)
            {
                if (eventLog.LogDisplayName == "Security")
                {
                    foreach (EventLogEntry entry in eventLog.Entries)
                    {
                        if (entry.TimeGenerated < _lastBruteForceAttackDetected.Add(_waitTimeBeforeANewBruteForceAttack) ||
                            entry.TimeGenerated < DateTime.Now.Subtract(_timeBeforeResetingBruteCounter)) //don't look at this event if a brute 
                            continue;                                                                     //force attack just happened or it is too far in the past
                        if (entry.InstanceId == 528 && _successfulLoginResetsBruteCounter)
                        {//event id 528 is a successful login
                            loginFailureDetected = false;
                            badLoginAttempts = 0;
                        }
                        else if (entry.InstanceId > 528 && entry.InstanceId < 540 && entry.InstanceId != 538)
                        {//all event Ids from 529 to 539 are for different login failures (except for 538 which is a logoff event)
                            if (loginFailureDetected && entry.TimeGenerated.Subtract(lastLoginFailure) > _timeBeforeResetingBruteCounter)
                                badLoginAttempts = 0;
                            loginFailureDetected = true;

                            lastLoginFailure = entry.TimeGenerated;
                            badLoginAttempts++;

                            if (badLoginAttempts == _numberOfBadLogins)
                            {
                                _lastBruteForceAttackDetected = entry.TimeGenerated;
                                
                                notificationMessage = "Brute Force Login Attack Detected On " + Environment.MachineName + " with last login attempt at " + entry.TimeGenerated.ToString();
                                Logs.WriteMessageToLog(notificationMessage);
                                Notifications.SendEmail(notificationMessage);
                                Console.WriteLine(notificationMessage);

                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Looks for Account Changes that could indicate an attack.
        /// </summary>
        /// <returns>Whether any account changes that could indicate an attack occured since the last time this function was called</returns>
        private static bool DetectAccountAndPolicyChanges()
        {
            Logs.WriteMessageToLog("Checking Event Logs for account changes"); //write a message to the logs

            bool anyAttacksDetected = false;
            DateTime accountCheckStarted = DateTime.Now;
            accountCheckStarted = accountCheckStarted.AddMilliseconds(-accountCheckStarted.Millisecond);

            List<EventLog> eventLogs = new List<EventLog>(EventLog.GetEventLogs());
            foreach (EventLog eventLog in eventLogs)
            {
                if (eventLog.LogDisplayName == "Security")
                {
                    string notificationMessage = "";
                    foreach (EventLogEntry entry in eventLog.Entries)
                    {
                        if (entry.TimeWritten < _accountChangesLastChecked.AddMilliseconds(-25))
                            continue;//if the event is old (give a little padding because reading/writing system event logs isn't instantaneous, don't look at it

                        if (entry.InstanceId == 624)//Checks if a new account has been added
                            notificationMessage = "A new account has been created on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 625)//Checks if an account type has been changed
                            notificationMessage = "An account type has changed on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 627)//Checks if an attempt was made to change an account password
                            notificationMessage = "An attempt was made to change an account password on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 628)//Checks if a new user password was set
                            notificationMessage = "A new account password was set on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 629)//Checks if an account was disabled
                            notificationMessage = "An account was disabled on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 630)//Checks if an account was deleted
                            notificationMessage = "An account was deleted on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 644)//Checks if an account was locked out
                            notificationMessage = "An account was locked out on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 517)//Checks if the audit logs were cleared
                            notificationMessage = "The audit logs were cleared on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 612)//Checks if the audit policy was changed
                            notificationMessage = "The audit policy was altered on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();
                        else if (entry.InstanceId == 642)//Checks if an unknown change was made to a user account
                            notificationMessage = "An unknown alteration was made to a user account on " + Environment.MachineName + " at " + entry.TimeGenerated.ToString();

                        if (notificationMessage != "")
                        {//send out an alert if an entry was found
                            Logs.WriteMessageToLog(notificationMessage);
                            Notifications.SendEmail(notificationMessage);
                            Console.WriteLine(notificationMessage);
                            notificationMessage = "";
                            anyAttacksDetected = true;
                        }
                    }
                }
            }

            _accountChangesLastChecked = accountCheckStarted;//update the last time checked
            return anyAttacksDetected;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Checks the event logs for account changes and look for brute force login attempts
        /// </summary>
        public static void CheckSecurityLogs()
        {
            DetectAccountAndPolicyChanges(); //check for account changes
            DetectBruteForceAttack();//check for brute force attacks
        }

        #endregion
    }
}
